package util

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"github.com/microcosm-cc/bluemonday"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
	"regexp"
	"strconv"
	"strings"
	"unicode"
)

const _alphabetAndNumbers = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

var _bmUGC, _bmStrict *bluemonday.Policy

var _fileNameReg *regexp.Regexp

func init() {
	_bmUGC = bluemonday.UGCPolicy()
	_bmStrict = bluemonday.StrictPolicy()
	_fileNameReg = regexp.MustCompile(`^[\x{4e00}-\x{9fa5}\w_\-.\s]+$`)
}

// StripXSS 去除XSS内容
func StripXSS(s string, isUGC bool) string {
	s = StrTrim(s)
	if s == "" {
		return ""
	}
	if isUGC {
		return _bmUGC.Sanitize(s)
	}
	return _bmStrict.Sanitize(s)
}

// StrTrim 去除字符串左右空白
func StrTrim(s string) string {
	return strings.TrimSpace(s)
}

// StrImplode 多个字符串拼接
func StrImplode(gap string, ss ...string) string {
	return strings.Join(ss, gap)
}

// StrMd5 计算字符串 MD5 值
func StrMd5(s string) string {
	bs := md5.Sum([]byte(s))
	return StrHexEncode(bs[:])
}

// StrSha1 计算字符串 Sha1 值
func StrSha1(s string) string {
	hash := sha1.New()
	hash.Write([]byte(s))
	return StrHexEncode(hash.Sum(nil))
}

// StrSha256 sha256加密
func StrSha256(s string) string {
	hash := sha256.New()
	hash.Write([]byte(s))
	return StrHexEncode(hash.Sum(nil))
}

// StrHexEncode 将16进制字节切片，转为字符串
func StrHexEncode(bs []byte) string {
	return hex.EncodeToString(bs)
}

// StrHexDecode 将16进制字符串，转为字节切片
func StrHexDecode(s string) ([]byte, error) {
	l := len(s)
	switch {
	case l == 0:
		return nil, nil
	case l > 1 && s[0:2] == "0x":
		return StrHexDecode(s[2:])
	case l%2 != 0:
		return StrHexDecode("0" + s)
	default:
		return hex.DecodeString(s)
	}
}

// StrRandom 得到指定长度的随机字符串
func StrRandom(l int, c string) string {
	if l <= 0 {
		return ""
	}
	if c == "" {
		c = _alphabetAndNumbers
	}
	sl := len(c)
	ret := make([]string, 0, l)
	for i := 0; i < l; i++ {
		ret = append(ret, string(c[RandInt(sl)]))
	}
	return StrImplode("", ret...)
}

// StrAToFloat64 字符串表示的10的-18次方，转为float64
func StrAToFloat64(s string) (f float64, err error) {
	l := len(s)
	if s == "0" {
		f = 0
		return
	} else if l == 18 {
		s = "0." + s
	} else if l < 18 {
		s = "0." + strings.Repeat("0", 18-l) + s
	} else {
		s = s[0:l-18] + "." + s[l-18:]
	}
	f, err = strconv.ParseFloat(s, 64)
	return
}

// StrToCamel 下划线转驼峰
func StrToCamel(s string) string {
	s = strings.Replace(s, "_", " ", -1)
	s = strings.Replace(s, "-", " ", -1)
	return strings.Replace(cases.Title(language.English).String(s), " ", "", -1)
}

// StrFromCamel 驼峰转下划线
func StrFromCamel(s, c string) string {
	if c == "" {
		c = "_"
	}
	l := len(s)
	if l == 0 {
		return ""
	}
	var b strings.Builder
	for i := 0; i < l; i++ {
		if unicode.IsUpper(rune(s[i])) {
			if i > 0 {
				b.WriteString(c)
			}
			b.WriteString(strings.ToLower(string(s[i])))
		} else {
			b.WriteString(string(s[i]))
		}
	}
	return b.String()
}

// StrStartWith 判断字符串 str 是否以 find 开始
func StrStartWith(str, find string) bool {
	return strings.HasPrefix(str, find)
}

// StrEndWith 判断字符串 str 是否以 find 结尾
func StrEndWith(str, find string) bool {
	return strings.HasSuffix(str, find)
}

// AddSlashes 返回在预定义字符之前添加反斜杠的字符串。
// 预定义字符是：单引号（'）、双引号（"）、反斜杠（\）
func AddSlashes(str string) string {
	var tmpRune []rune
	strRune := []rune(str)
	for _, ch := range strRune {
		switch ch {
		case []rune{'\\'}[0], []rune{'"'}[0], []rune{'\''}[0]:
			tmpRune = append(tmpRune, []rune{'\\'}[0])
			tmpRune = append(tmpRune, ch)
		default:
			tmpRune = append(tmpRune, ch)
		}
	}
	return string(tmpRune)
}

// StripSlashes 删除由 AddSlashes() 函数添加的反斜杠。
func StripSlashes(str string) string {
	var dstRune []rune
	strRune := []rune(str)
	strLength := len(strRune)
	for i := 0; i < strLength; i++ {
		if strRune[i] == []rune{'\\'}[0] {
			i++
		}
		dstRune = append(dstRune, strRune[i])
	}
	return string(dstRune)
}

// StrHashPass 对密码做Hash
func StrHashPass(pass string) (string, error) {
	bs, err := bcrypt.GenerateFromPassword([]byte(pass), 14)
	return string(bs), err
}

// StrCheckPass 验证密码，是否能匹配
func StrCheckPass(pass, hash string) bool {
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pass)) == nil
}

// StrSubZh 中文字符串截取
func StrSubZh(str string, start, end uint) string {
	sRune := []rune(str)
	if sLen := len(sRune); int(start) >= sLen || int(end) >= sLen || start > end {
		return str
	}
	return string(sRune[start:end])
}

// StrNiceSize 友好方式显示文件大小
func StrNiceSize(sizeI int64) string {
	i := 0
	size := float64(sizeI)
	for ; i < 4 && size >= 1024; i++ {
		size /= 1024.0
	}
	units := [5]string{"Bytes", "KB", "MB", "GB", "TB"}
	return fmt.Sprintf("%.2f%s", size, units[i])
}

// StrFileName 字符串能否作为文件名
func StrFileName(name string) (string, error) {
	name = StrTrim(name)
	if l := len(name); l == 0 || l > 255 {
		return "", fmt.Errorf("length of name should be 1~255")
	}
	if !_fileNameReg.MatchString(name) {
		return "", fmt.Errorf("invaild file name")
	}
	return name, nil
}
